<?php
/*

  Copyright (c) 2008   by  maxim.oransky <maxim.oransky@gmail.com>,
  auhtor of  the-wheel-library  team

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

class WebHorse {

	const MAX_ATTEMPTS_WHILE_EMPTY_CONTENT = 3;

    protected function getHttpUserAgent() {
        return 'Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.8.0.7) Gecko/20060909 Firefox/1.5.0.7';
    }

    protected static $instanceCounter = 0;

    public function getUrl() {
        return $this->url;
    }

    /**
     * @var Url
     */
    protected $oSiteUrl;
    protected $id;

    /**
     * @return WebHorseLoadedPage
     */
    public static function singlePageRequest($request) {
        $oUrl = Url::create($request);
        $horse = new WebHorse( Url::create($oUrl->getScheme() . '://' . $oUrl->getSite()) );
        return $horse->requestPage($oUrl->getPath());
    }

    public function __construct(Url $oSiteUrl, $id = false) {

        if ($oSiteUrl->getPath() || $oSiteUrl->getAncor() || $oSiteUrl->getParamsAsHash())
            throw new Exception('Site url must be "http://site.domain" format');

        self::$instanceCounter++;

        $this->oSiteUrl = $oSiteUrl;
        $this->id = $id ? $id : mt_rand();
    }

    public function resetCookies() {
        if (file_exists($this->getCookieFileName()))
            unlink($this->getCookieFileName());
    }

    /**
     * @return WebHorseLoadedPage
     */
    public function requestPage($path) {
        return $this->doRequest($path);
    }

    /**
     * @return WebHorseLoadedPage
     */
    public function sendPost($path, array $hPostParams) {
        return $this->doRequest($path, array(), $hPostParams);
    }

    /**
     * @return WebHorseLoadedPage
     */
    public function sendGet($path, array $hGetParams) {
        return $this->doRequest($path, $hGetParams);
    }

    protected $serverBlankResponseCounter = 0;

    /**
     * @return WebHorseLoadedPage
     */
    protected function doRequest($path, array $hGetParams = array(), array $hPostParams = array(), $redirectNumber = 0) {

    	if (empty($path)) $path = '/';

    	$path = Url::trimAncor($path);

        $ch = curl_init();
        //curl_setopt($ch, CURLOPT_VERBOSE, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // not compl. with safe mode

        $oRequestedUrl = Url::create(
            $this->oSiteUrl->composeAbsoluteLink() . $path
        );

        if ($hGetParams) {
            foreach ($hGetParams as $param => $value) {
                $oRequestedUrl->addParam($param, $value);
            }
        }

//        File::save('d:\LASTTTT' . time(), $oRequestedUrl->composeAbsoluteLink());
//        File::save('d:\LASTTTTa' . time(), print_r($oRequestedUrl,1));

        curl_setopt($ch, CURLOPT_URL, $oRequestedUrl->composeAbsoluteLink());

        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,  1);

        curl_setopt($ch, CURLOPT_USERAGENT,  $this->getHttpUserAgent());

        curl_setopt($ch, CURLOPT_COOKIEJAR,  $this->getCookieFileName());
        curl_setopt($ch, CURLOPT_COOKIEFILE, $this->getCookieFileName());

        curl_setopt($ch, CURLOPT_HEADER, 1);

        if ($hPostParams) {

			curl_setopt($ch, CURLOPT_HTTPHEADER, array(
				"Content-Encoding: UTF-8"
			));

            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, self::concatParams($hPostParams));
        }

        $serverResponse = curl_exec($ch);
        $hInfo = curl_getinfo($ch);
        $effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);

        curl_close($ch);

        $header  = substr($serverResponse, 0, $hInfo['header_size']);
        $content = trim(substr($serverResponse, $hInfo['header_size']));

        $oRedirectedUrl = Url::create( $this->decodeGet($effectiveUrl) );

        if ($hInfo['http_code'] == 302) {

            if ($redirectNumber > 3)
                $this->fatalError("Redirect loop!");

            if (!preg_match('/^Location: (.*)$/m',$header,$m))
                $this->fatalError("cannot select location from [$header]");

            $location = $m[1];

            return $this->doRequest($location, $hGetParams, $hPostParams, $redirectNumber + 1);
        }

        if (!$content) {

            if ($this->serverBlankResponseCounter > self::MAX_ATTEMPTS_WHILE_EMPTY_CONTENT)
                throw new Exception('Empty response while trying to get ' . $path);

            $this->serverBlankResponseCounter++;
            echo('<p><tt>Empty content - ' . $this->serverBlankResponseCounter . ' waiting 5 sec...</tt>[' . print_r($hInfo,1) . ']</p>');
            sleep(5);

            return $this->doRequest($path, $hGetParams, $hPostParams, $redirectNumber);
        }

        $this->serverBlankResponseCounter = 0;

        return new WebHorseLoadedPage(
            $content,
            $header,
            $oRequestedUrl,
            $oRedirectedUrl
        );
    }

    protected function getCookieFileName() {
        return Env::getTmpDir() . get_class() . self::$instanceCounter . '-' . $this->id . '.cookie.tmp';
    }

    protected function concatParams($hParams) {

        $res = '';
        foreach ($hParams as $key => $val) {

            if (is_array($val)) {

                foreach ($val as $subval) {
                    $res .= $key . '[]=' . self::escape($subval) . '&';
                }

            } else {
                $res .= $key . '=' . self::escape($val) . '&';
            }
        }
        return $res;
    }

    protected function escape($str) {
        return strtr($str, array(
            '+' => '%2B',
        ));
    }

    protected function decodeGet($str) {
        $str = strtr($str,array('+' => ' '));
        return preg_replace_callback('~%([0-9A-F]{2})~', array(get_class(),'decodeGetSymbol'), $str);
    }

    protected function decodeGetSymbol($m) {
        return chr(hexdec($m[1]));
    }

    protected function fatalError($msg) {
		throw new Exception($msg);
    }
}


class WebHorseLoadedPage {

    private $content;
    private $headers;
    private $oRequestedUrl;
    private $oUrl;

    public function getContent() { return $this->content; }

    public function getHeaders() { return $this->headers; }

    /**
     * @return Url
     */
    public function getRequestedUrl() { return $this->oRequestedUrl; }

    /**
     * @return Url
     */
    public function getUrl() { return $this->oUrl; }

    public function __construct(
        $content,
        $headers,
        Url $oRequestedUrl,
        Url $oUrl
    ) {
        $this->content = $content;
        $this->headers = $headers;
        $this->oRequestedUrl = $oRequestedUrl;
        $this->oUrl = $oUrl;
    }
}